package org.piggy.core.manager;

import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.apache.commons.collections.CollectionUtils;
import org.piggy.common.config.ServiceDefinition;
import org.piggy.common.config.ServiceInstance;
import org.piggy.common.constants.OtherConst;
import org.piggy.common.rule.Rule;
import org.piggy.core.algorithm.PathTrie;
import org.piggy.core.algorithm.ConsistentHash;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


public class DynamicConfigManager {

	private final ConcurrentHashMap<String ,ServiceDefinition> serviceDefinitionMap = new ConcurrentHashMap<>();

	private final ConcurrentHashMap<String, Set<ServiceInstance>> serviceInstanceMap = new ConcurrentHashMap<>();

	private final ConcurrentHashMap<String, ConsistentHash> consistentHashMap = new ConcurrentHashMap<>();

	private final ConcurrentHashMap<String, ConsistentHash> grayConsistentHashMap = new ConcurrentHashMap<>();

	private TreeMap<String, Rule> pathRuleMap = new TreeMap<>();
	private PathTrie<Rule> rulePathTrie = new PathTrie<>();

	private DynamicConfigManager() {
	}

	private static class SingletonHolder {
		private static final DynamicConfigManager INSTANCE = new DynamicConfigManager();
	}

	public static DynamicConfigManager getInstance() {
		return SingletonHolder.INSTANCE;
	}

	public void putServiceDefinition(String serviceName, ServiceDefinition serviceDefinition) {
		serviceDefinitionMap.put(serviceName, serviceDefinition);;
	}

	public ServiceDefinition getServiceDefinition(String serviceName) {
		return serviceDefinitionMap.get(serviceName);
	}

	public void removeServiceDefinition(String serviceName) {
		serviceDefinitionMap.remove(serviceName);
	}

	public ConcurrentHashMap<String, ServiceDefinition> getServiceDefinitionMap() {
		return serviceDefinitionMap;
	}

	public Set<ServiceInstance> getServiceInstanceByServiceName(String serviceName, boolean gray){
		Set<ServiceInstance> serviceInstances = serviceInstanceMap.get(serviceName);
		if (CollectionUtils.isEmpty(serviceInstances)) {
			return Collections.emptySet();
		}
		if (gray) {
			return serviceInstances.stream().filter(ServiceInstance::isGray).collect(Collectors.toSet());
		}else{
			return serviceInstances.stream().filter(serviceInstance -> !serviceInstance.isGray()).collect(Collectors.toSet());
		}
	}

	public ConsistentHash getConsistentHashByServiceName(String serviceName, boolean gray) {
		if(gray){
			return grayConsistentHashMap.get(serviceName);
		}else{
			return consistentHashMap.get(serviceName);
		}
	}

	public void addServiceInstance(String serviceName, ServiceInstance serviceInstance) {
		Set<ServiceInstance> set = serviceInstanceMap.get(serviceName);
		set.add(serviceInstance);
	}

	public void addServiceInstance(String serviceName, Set<ServiceInstance> serviceInstanceSet) {
		// 通用规则
		serviceInstanceMap.put(serviceName, serviceInstanceSet);

		// hash 规则使用
		// gray == true
		ConsistentHash consistentHash1 = new ConsistentHash();
		// gray == false
		ConsistentHash consistentHash2 = new ConsistentHash();
		for(ServiceInstance instance : serviceInstanceSet){
			if(instance.isGray()){
				consistentHash1.addNode(instance.getServiceInstanceId());
			}else{
				consistentHash2.addNode(instance.getServiceInstanceId());
			}
		}
		grayConsistentHashMap.put(serviceName, consistentHash1);
		consistentHashMap.put(serviceName, consistentHash2);
	}

	public void updateServiceInstance(String serviceName, ServiceInstance serviceInstance) {
		Set<ServiceInstance> set = serviceInstanceMap.get(serviceName);
		Iterator<ServiceInstance> it = set.iterator();
		while(it.hasNext()) {
			ServiceInstance is = it.next();
			if(is.getServiceInstanceId().equals(serviceInstance.getServiceInstanceId())) {
				it.remove();
				break;
			}
		}
		set.add(serviceInstance);
	}

	public void removeServiceInstance(String serviceName, String serviceInstanceId) {
		Set<ServiceInstance> set = serviceInstanceMap.get(serviceName);
		Iterator<ServiceInstance> iterator = set.iterator();
		while(iterator.hasNext()) {
			ServiceInstance instance = iterator.next();
			if(instance.getServiceInstanceId().equals(serviceInstanceId)) {
				iterator.remove();
				break;
			}
		}
	}

	public void removeServiceInstancesByServiceName(String serviceName) {
		serviceInstanceMap.remove(serviceName);
	}

	public void putAllPathRule(List<Rule> ruleList) {
		TreeMap<String, Rule> treeMap = new TreeMap<>(Comparator.reverseOrder());
		for(Rule rule : ruleList){
			treeMap.put(rule.getPrefix(), rule);
			List<String> paths = rule.getPaths();
			for (String path : paths){
				treeMap.put(path, rule);
			}
		}
		this.pathRuleMap = treeMap;
	}

	public void putAllPathRulePro(List<Rule> ruleList) {
		PathTrie<Rule> rulePathTrie = new PathTrie<>();
		for(Rule rule : ruleList){
			rulePathTrie.insert(rule.getPrefix(), rule);
		}
		this.rulePathTrie = rulePathTrie;
	}

	public Rule getRulePro(String path) {
		return rulePathTrie.match(path);
	}

	public void initFlowQpsRule(List<Rule> ruleList) {
		List<FlowRule> flowRuleList = new ArrayList<>();
		for (Rule rule : ruleList){
			Rule.FlowControlConfig flowControlConfig = rule.getFlowControlConfig();
			if(flowControlConfig != null){
				Integer count = flowControlConfig.getCount();
				if(count!= null && count > 0){
					FlowRule flowRule = new FlowRule();
					flowRule.setResource(OtherConst.FLOW + rule.getId());
					flowRule.setCount(count);
					flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
					flowRuleList.add(flowRule);
				}
			}
		}
		FlowRuleManager.loadRules(flowRuleList);
	}

	// serviceInstanceId = ip:port
	public void initDegradeRule(List<Rule> ruleList) {
		List<DegradeRule> rules = new ArrayList<>();
		for (Rule rule : ruleList){
			Rule.CircuitBreakerConfig circuitBreakerConfig = rule.getCircuitBreakerConfig();
			if(circuitBreakerConfig != null){
				Integer count = circuitBreakerConfig.getCount();
				if(count != null && count > 0){
					DegradeRule degradeRule = new DegradeRule(OtherConst.DEGRADE + rule.getId())
							.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
							// 慢调用比例模式下为慢调用临界 RT（超出该值计为慢调用）ms
							.setCount(count)
							// 熔断时长（单位为 s）
//							.setTimeWindow(circuitBreakerConfig.getTimeWindow())
							.setTimeWindow(10)
							// 慢调用比例阈值，仅慢调用比例模式有效
//							.setSlowRatioThreshold(circuitBreakerConfig.getSlowRatioThreshold())
							.setSlowRatioThreshold(0.2)
							// 熔断触发的最小请求数，请求数小于该值时即使异常比率超出阈值也不会熔断
//							.setMinRequestAmount(circuitBreakerConfig.getMinRequestAmount())
							.setMinRequestAmount(5)
							// 统计时长（单位为 ms）
							.setStatIntervalMs(circuitBreakerConfig.getStatIntervalMs());
					rules.add(degradeRule);
				}
			}
		}
		DegradeRuleManager.loadRules(rules);
	}

	public Rule getRuleByPath(String path) {
		return pathRuleMap.get(path);
	}

	public Rule getRule(String path) {
		Set<Map.Entry<String, Rule>> entries = pathRuleMap.entrySet();
		for (Map.Entry<String, Rule> entry : entries){
			String key = entry.getKey();
			if(path.startsWith(key)){
				return entry.getValue();
			}
		}
		return null;
	}

	public TreeMap<String, Rule> getPathRuleMap(){
		return pathRuleMap;
	}

}
